<?php
// Autor (c) Miroslav Novak, www.platiti.cz
// Pouzivani bez souhlasu autora neni povoleno
// #Ver:PRV043#


class PayuGate {

	var $config;


	// konstruktor PayuGate($config)
	// vytvori objekt a nastavi konfiguraci
	// $config - konfiguracni asociativni pole
	//		pos_id	- hodnota, kterou přidělilo PayU
	//		pos_auth_key	- Autorizační klíč POS (pos_auth_key)
	//		url_payu    - adresa URL, na které byla nainstalována aplikace PayU https://www.payu.cz/paygw/	
	//		encoding	- Kódování přenášených dat. Jedna z následujících hodnot: ISO, UTF, WIN
	//		key1	- Klíč (MD5)
	//		key2	- Druhý klíč (MD5)
	//		log_file	- název log souboru

    function PayuGate($config) {
		$this->assertStruct($config, array(
			'pos_id',
			'pos_auth_key' ,
			'url_payu',
			'encoding',
			'key1',
			'key2',
			'log_file'
		), array(
		));
		$this->config = $config;
	}
	

	// funkce sendOrder($order)
	// 	presmeruje browser klienta na branu payu
	// $order - asociativni pole parametry platby
	//		session_id	- ID platby – jedinečné pro zákazníka, hodnota přidělena aplikací Obchodu při vytvoření transakce
	//		amount	- částka v haléřích
	//		desc	- krátký popis – objeví se zákazníkovi, na výpisech z banky a jiných místech
	//		first_name	- jméno
	//		last_name	- příjmení
	//		email	- e-mailová adresa
	//	nepovinne:
	//		order_id	- číslo objednávky, hodnota přidělena aplikací Obchodu při vytvoření transakce
	//		desc2	- libovolná informace
	//		street	- ulice
	//		street_hn	- domovní číslo
	//		street_an	- číslo bytu
	//		city	- město
	//		post_code	- PSČ
	//		country	- kód krajiny zákazníka (2 písmena) dle ISO-3166 http:www.chemie.fu-berlin.de/diverse/doc/ISO_3166.html
	//		phone	- telefonní číslo, je možné zadat několik čísel oddělených čárkami
	//		language	- kód jazyka dle ISO-639 http:www.ics.uci.edu/pub/ietf/http/related/iso639.txt (currently cs, en)
	//		pay_type	- kód platby dle dokumentace PayU v tabulce typy plateb, pokud je zadan nevybira jej uz zakaznik na brane

	function sendOrder($order) {
		echo sendOrderHtml($order);
	}
	
	function sendOrderHtml($order) {
		$this->assertStruct($order, array(
			'session_id',
			'amount',
			'desc',
			'first_name',
			'last_name',
			'email',
		), array(
			'order_id',
			'desc2',
			'street',
			'street_hn',
			'street_an',
			'city',
			'post_code',
			'country',
			'phone',
			'language',
			'pay_type',   // nedokumentovano, ale lze vynutit konkretni platebni metody v payu a preskocit vyber
		));

		$client_ip = $_SERVER['REMOTE_ADDR'];
		
		$order['pos_id'] = $this->config['pos_id'];
		$order['pos_auth_key'] = $this->config['pos_auth_key'];
		$order['client_ip'] = $client_ip;
		$order['ts'] = time();
		$varsig = array('pos_id', 'pay_type', 'session_id', 'pos_auth_key', 'amount', 'desc', 'desc2'
, 'order_id', 'first_name', 'last_name', 'street', 'street_hn', 'street_an', 'city', 'post_code', 'country', 'email', 'phone', 'language', 'client_ip', 'ts');
		$dig = "";
		foreach ($varsig as $n) {
			if (isset($order[$n])) {
				$dig .= $order[$n];
			}
		}
		$dig .= $this->config['key1'];
		$order['sig'] = md5($dig);
		$url = $this->config['url_payu'].$this->config['encoding']."/NewPayment";
		
		// create hidden autosubmit
		$form = '<form id="form" method="post" action="'.$url.'">';
		foreach($order as $name=>$value) {
			$form.='<input type="hidden" name="'.$name.'" value="'.$value.'">';
		}
		$form .= '<input type="hidden" name="js" value="0">';
		$form .= "<noscript>Klikněte prosím na tlačítko / Please click the button<br/><input type='submit' value='Pokracovat / Continue'></noscript>";
		$form .= "</form>";
		$form .= "<script>document.getElementById(\"form\").js.value=1; document.getElementById(\"form\").submit();</script>";

		$this->writeLog("gateRedirect ".$form);
		return "<html><body>".$form."</body></html>";

	}



	// funkce receiveReply(&$trans_data)
	// pro zavolani z positive/negative scriptu
	//				spravny format positive i negative scriptu: http[s]://server/script?trans_id=%transId%&pos_id=%posId%&pay_type=%payType%&session_id=%sessionId%&amount_ps=%amountPS%&amount_cs=%amountCS%&order_id=%orderId%&error=%error%
	// zpracuje predane hodnoty a vrati je
	// $trans_data - navratove asociativni pole s parametry platby
	//		trans_id	- identifikátor nové transakce vytvořený v aplikaci PayU
	//		pos_id	- hodnota, kterou přidělilo PayU
	//		session_id	- ID platby – jedinečné pro zákazníka, hodnota přidělena aplikací Obchodu při vytvoření transakce
	//		pay_type	- typ platby - jeden z tabulky 2.4 Typy Plateb
	//		error	- Číslo chyby payu
	//		order_id	- číslo objednávky, hodnota přidělena aplikací Obchodu při vytvoření transakce
	// navratova hodnota
	//		0	- ok
	//		-1	- neodpovida pos_id
	//		-3	- chybejici parametry - pravdepodobne spatny format placeholderu url pro positive a negative
	//		kladna hodnota	- cislo chyby payu
	
	function receiveReply(&$trans_data) {

		$this->writeLog("gateReply ".$_SERVER["QUERY_STRING"]);

		if(!isset($_GET['trans_id']) || !isset($_GET['pos_id']) || !isset($_GET['session_id']) || !isset($_GET['pay_type']) || !isset($_GET['error']) || !isset($_GET['order_id'])) {
			$result = -3; // chybejici parametry
		}
		if (isset($_GET['error']) && $_GET['error']!='%error%') {
			$result = $_GET['error'];
		} else {
			$result = 0;
		}
		$trans_data = $_GET;
		$trans_data['error']=$result;

		if ($_GET['pos_id'] != $this->config['pos_id']) {
			$result = -1; // neodpovida pos_id
		}
		
		
		return $result;
	}

        // receiveNotificationAndGet(trans_data)
        // pro zavolani z online scriptu. 
        // zpracuje predane hodnoty, overi jejich podpis, dale zavola proceduru Get a vrati jeji vysledek
        // Po zavolani teto funkce je potreba zavola funkci closeNotification
        //
        // trans_data	- vracene udaje o transakci z callGet
        // navratova hodnota
        //		0	- ok
        //		-1	- neplatne pos_id
        //		-2	- chybny podpis
        //		-3	- chybejici parametry - spatne formatovany online dotaz brany

        function receiveNotificationAndGet(&$trans_data) {
		
			$result = $this->receiveNotification($session_id);
			$getResult = $result;
			if ($result==0) {
				$getResult = $this->callGet($session_id, $trans_data);
			} else {
				$trans_data = null;
			};
            return $getResult;
        }


        // closeNotification(precessResult)
        // odpovi v ramci "online" skriptu, pokud je parametr "OK" brana povazuje notifikaci za zpracovanou, jinak bude volani "online" skriptu opakovat
        function closeNotification($processResult) {
            $this->writeLog("closeNotification " . ($processResult != "OK" ? "Error: " : "") . $processResult);
            echo $processResult;
        }

	
	// funkce receiveNotification(&$session_id)
	// pro zavolani z online scriptu. 
	// Funkce zpracuje predane hodnoty, overi jejich podpis, a vrati je
	// S vyslednym $session_id je po te vhodne zavolat funkci callGet pro zjisteni stavu platby.
	// nasledne vypsat prace text "OK", je-li zpracovani uspesne. Jinak payu bude tuto notifikaci opakovat.
	//
	// $session_id	- vracena hodnota identifikace platby
	// navratova hodnota
	//		0	- ok
	//		-1	- neplatne pos_id
	//		-2	- chybny podpis
	//		-3	- chybejici parametry - spatne formatovany online dotaz brany

	function receiveNotification(&$session_id) {
		if(!isset($_POST['pos_id']) || !isset($_POST['session_id']) || !isset($_POST['ts']) || !isset($_POST['sig'])) {
			$result = -3; // chybejici parametry
		} else if ($_POST['pos_id'] != $this->config['pos_id']) {
			$result = -1;  // neodpovida pos_id
		} else {
			$sig = md5( $_POST['pos_id'] . $_POST['session_id'] . $_POST['ts'] . $this->config['key2']);  // --- verifikace obdrženého podpisu (signature)
			if ($_POST['sig'] != $sig) {
				$result = -2;   //--- chybný podpis (signature)
			} else {
				$session_id = $_POST['session_id'];
				$result = 0;
			}
		}
		$this->writeLog('receiveNotification recv result='.$result.'  data: '.var_export($_POST, true));
		return $result;
	}

	
	// funkce callGet($session_id, &$trans_data)
	// vyvola payu proceduru Payment/get pro zjisteni stavu transakce
	// $session_id	- identifikace platby
	// $trans_data - navratove asociativni pole s parametry platby
	//		pos_id	- hodnota, kterou přidělilo PayU
	//		session_id	- ID platby – jedinečné pro zákazníka, hodnota přidělena aplikací Obchodu při vytvoření transakce
	//		amount	- částka v haléřích
	//		desc	- krátký popis – objeví se zákazníkovi, na výpisech z banky a jiných místech
	//		order_id	- číslo objednávky, hodnota přidělena aplikací Obchodu při vytvoření transakce
	//		desc2	- libovolná informace
	//		sig	- kontrolní součet parametrů formuláře zaslaného platformě
	//		ts	- časová známka použitá na výpočet hodnoty sig
	//		trans_id	- identifikátor nové transakce vytvořený v aplikaci PayU
	//		pay_type	- typ platby - jeden z tabulky 2.4 Typy Plateb
	//		status	- aktuální stav transakce v souladu s bodem 2.2
	//		pay_gw_name	- název brány vykonávající transakci – interní informace aplikace PayU
	//		create	- datum vytvoření transakce
	//		init	- datum začátku transakce
	//		sent	- datum, kdy byla transakce předána na vybrání
	//		recv	- datum přijetí transakce
	//		cancel	- datum zrušení transakce
	//		auth_fraud	- interní informace aplikace PayU
	//		add_test	- vždy hodnota „1“
	//		add_testid	- id transakce
	// navratova hodnota
	//		0	- ok
	//		-2	- neplatny podpis
	//		kladna hodnota	- cislo chyby payu
	
	function callGet($session_id, &$trans_data) {
		$ts = time();
		$sig = md5( $this->config['pos_id'] . $session_id . $ts . $this->config['key1']);  // --- podpis (signature), který bude odeslán do PayU spolu s požadavkem (request)
		$parameters = "pos_id=" . $this->config['pos_id'] . "&session_id=" . $session_id . "&ts=" . $ts . "&sig=" . $sig;
		$url = $this->config['url_payu'].$this->config['encoding']."/Payment/get";
		
		$this->writeLog('callGet send '.$url.', '.$parameters);
		
		$payu_response = $this->loadUrl($url,$parameters);

		//$this->writeLog('callGet recv raw '.$payu_response);
		
		$trans_data=array();
		$status_zprac = $this->grep($payu_response, '<status>(.*?)</status>');
		if ($status_zprac != "OK") {
			$payu_response = $this->grep($payu_response, '<error>(.*?)</error>');
			$trans_data['error']=$this->grep($payu_response, '<nr>(.*?)</nr>');
			$trans_data['message']=$this->grep($payu_response, '<message>(.*?)</message>');
			
			$result = $trans_data['error'];
			
		} else {
			$payu_response = $this->grep($payu_response, '<trans>(.*?)</trans>');
			$trans_data['trans_id']=$this->grep($payu_response, '<id>(.*?)</id>');
			$trans_data['pos_id']=$this->grep($payu_response, '<pos_id>(.*?)</pos_id>');
			$trans_data['session_id']=$this->grep($payu_response, '<session_id>(.*?)</session_id>');
			$trans_data['order_id']=$this->grep($payu_response, '<order_id>(.*?)</order_id>');
			$trans_data['amount']=$this->grep($payu_response, '<amount>(.*?)</amount>');
			$trans_data['status']=$this->grep($payu_response, '<status>(.*?)</status>');
			$trans_data['pay_type']=$this->grep($payu_response, '<pay_type>(.*?)</pay_type>');
			$trans_data['pay_gw_name']=$this->grep($payu_response, '<pay_gw_name>(.*?)</pay_gw_name>');
			$trans_data['desc']=$this->grep($payu_response, '<desc>(.*?)</desc>');
			$trans_data['desc2']=$this->grep($payu_response, '<desc2>(.*?)</desc2>');
			$trans_data['create']=$this->grep($payu_response, '<create>(.*?)</create>');
			$trans_data['init']=$this->grep($payu_response, '<init>(.*?)</init>');
			$trans_data['sent']=$this->grep($payu_response, '<sent>(.*?)</sent>');
			$trans_data['recv']=$this->grep($payu_response, '<recv>(.*?)</recv>');
			$trans_data['cancel']=$this->grep($payu_response, '<cancel>(.*?)</cancel>');
			$trans_data['auth_fraud']=$this->grep($payu_response, '<auth_fraud>(.*?)</auth_fraud>');
			$trans_data['ts']=$this->grep($payu_response, '<ts>(.*?)</ts>');
			$trans_data['sig']=$this->grep($payu_response, '<sig>(.*?)</sig>');
			$trans_data['add_test']=$this->grep($payu_response, '<add_test>(.*?)</add_test>');
			$trans_data['add_testid']=$this->grep($payu_response, '<add_testid>(.*?)</add_testid>');

			$sig = md5($trans_data['pos_id'] . $trans_data['session_id'] . $trans_data['order_id'] . $trans_data['status'] . $trans_data['amount'] . $trans_data['desc'] . $trans_data['ts'] . $this->config['key2']);
			
			if ($sig == $trans_data['sig']) {
				$result = 0;
			} else {
				$result = -2;  //neplatny podpis
			}

		}
		$this->writeLog('callGet recv result='.$result." data ".var_export($trans_data, true));
		return $result;
	}

	// funkce callConfirmCancel($confirm_cancel, $session_id, &$trans_data)
	// vyvola payu proceduru Payment/confirm nebo Payment/cancel
	// $confirm_cancel - rozliseni metody "confirm" nebo "cancel"
	// $session_id	- identifikace platby
	// $trans_data - navratove asociativni pole s parametry platby - prakticky neuzitecne
	//		trans_id	- identifikátor nové transakce vytvořený v aplikaci PayU
	//		pos_id	- hodnota, kterou přidělilo PayU
	//		session_id	- ID platby – jedinečné pro zákazníka, hodnota přidělena aplikací Obchodu při vytvoření transakce
	//		ts	- časová známka použitá na výpočet hodnoty sig
	//		sig	- kontrolní součet parametrů formuláře zaslaného platformě
	// navratova hodnota
	//		0	- ok
	//		-2	- neplatny podpis
	//		kladna hodnota	- cislo chyby payu
	
	function callConfirmCancel($confirm_cancel, $session_id, &$trans_data) {
		if ($confirm_cancel != "confirm" && $confirm_cancel != "cancel" ) {
			user_error('Neplatny parametr confirm_cancel');
		}
		$ts = time();
		$sig = md5( $this->config['pos_id'] . $session_id . $ts . $this->config['key1']);  // --- podpis (signature), který bude odeslán do PayU spolu s požadavkem (request)
		$parameters = "pos_id=" . $this->config['pos_id'] . "&session_id=" . $session_id . "&ts=" . $ts . "&sig=" . $sig;
		$url = $this->config['url_payu'].$this->config['encoding']."/Payment/".$confirm_cancel;
		
		$this->writeLog('callConfirmCancel '.$confirm_cancel.' send '.$url.', '.$parameters);
		
		$payu_response = $this->loadUrl($url,$parameters);

		//$this->writeLog('callConfirmCancel recv raw '.$payu_response);
		
		$trans_data=array();
		$status_zprac = $this->grep($payu_response, '<status>(.*?)</status>');
		if ($status_zprac != "OK") {
			$payu_response = $this->grep($payu_response, '<error>(.*?)</error>');
			$trans_data['error']=$this->grep($payu_response, '<nr>(.*?)</nr>');
			$trans_data['message']=$this->grep($payu_response, '<message>(.*?)</message>');
			
			$result = $trans_data['error'];
			
		} else {
			$payu_response = $this->grep($payu_response, '<trans>(.*?)</trans>');
			$trans_data['trans_id']=$this->grep($payu_response, '<id>(.*?)</id>');
			$trans_data['pos_id']=$this->grep($payu_response, '<pos_id>(.*?)</pos_id>');
			$trans_data['session_id']=$this->grep($payu_response, '<session_id>(.*?)</session_id>');
			$trans_data['ts']=$this->grep($payu_response, '<ts>(.*?)</ts>');
			$trans_data['sig']=$this->grep($payu_response, '<sig>(.*?)</sig>');

			$sig = md5($trans_data['pos_id'] . $trans_data['session_id'] . $trans_data['ts'] . $this->config['key2']);
			
			if ($sig == $trans_data['sig']) {
				$result = 0;
			} else {
				$result = -2;  //neplatny podpis
			}

		}
		$this->writeLog('callConfirmCancel recv result='.$result." data ".var_export($trans_data, true));
		return $result;
	}

	
	////////////////////////////////////////////////////// vnitrni funkce
	
	function loadUrl($url, $post_data='') {
	
		$fsocket = false;
		$curl = false;
		$result = false;
	
		$urlParts = parse_url($url);
		$server = $urlParts['host'];
		$script = $urlParts['path'];
		if (isset($urlParts['query'])) {
			$script .= '?'.$urlParts['query'];
		}
		
		if ( (PHP_VERSION >= 4.3) && ($fp = @fsockopen('ssl://' . $server, 443, $errno, $errstr, 30)) ) {
			$fsocket = true;
		} elseif (function_exists('curl_exec')) {
			$curl = true;
		}

	
		if ($fsocket == true) {
			$header = 'POST ' . $script . ' HTTP/1.0' . "\r\n" .
			'Host: ' . $server . "\r\n" .
			'Content-Type: application/x-www-form-urlencoded' . "\r\n" .
			'Content-Length: ' . strlen($post_data) . "\r\n" .
			'Connection: close' . "\r\n\r\n";
			@fputs($fp, $header . $post_data);
			$payu_response = '';
			while (!@feof($fp)) {
				$res = @fgets($fp, 1024);
				$payu_response .= $res;
			}
			@fclose($fp);
			
		} elseif ($curl == true) {
			$ch = curl_init();
			curl_setopt($ch, CURLOPT_URL, $url);
			curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
			curl_setopt($ch, CURLOPT_HEADER, 0);
			curl_setopt($ch, CURLOPT_TIMEOUT, 20);
			curl_setopt($ch, CURLOPT_POST, 1);
			curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
			curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
			$payu_response = curl_exec($ch);
			curl_close($ch);
		} else {
			die("ERROR: No connect method ...\n");
		}
		
		return $payu_response;
	}
	
	
	function assertStruct($struct, $fieldsMandatory, $fieldsOptional) {
		foreach($struct as $key=>$val) {
			if (in_array($key, $fieldsMandatory)) {
				unset($fieldsMandatory[array_search($key, $fieldsMandatory)]);
			} else if(in_array($key, $fieldsOptional)) {
			} else {
				user_error("Unexpected field: ".$key);
			}
		}
		if (!empty($fieldsMandatory)) {
			user_error("Missing mandatory keys: ".implode(",",$fieldsMandatory));
		}
	}		
	
	function writeLog($s) {
		$log = fopen($this->config['log_file'], "a");
		$s = str_replace("\r","",str_replace("\n"," ",$s));
		fputs($log,  "*** " .date('r')."  ".$s."\n");
		fclose($log);
	}

	function grep($text, $pattern) {
		preg_match('/'.str_replace('/','\/',$pattern).'/s', $text, $parts);
		if (isset($parts[1])) {
			return $parts[1];
		} else {
			return null;
		}
	}
}

	
